/*
 *  GuiFont.java
 *
 *  Created on 5 de junio de 2007
 *
 *  @author     Lobsang Alvites Szaler (Mr.Byte)
 *  @version    0.20
 *  @company    Soluciones Info Moviles (SIM)
 *
 */

package sim.framework.font;

import java.util.*;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Graphics;

import sim.framework.gui.*;
import sim.framework.font.*;


public class FontSingle {
    
    private FontSystem  m_FS;           // [RESERVED] reference to FontSystem
    
    private Image	img;            // font-set image
    private short	height;         // [QUERY] font height
    private short	chroffset;      // [RESERVED] ascii offset for charset beginning
    private short[]	propClipX;      // [RESERVED] precalc clipping info
    private short[]	propClipY;      // [RESERVED] precalc clipping info
    private short[]	propOffY;       // [RESERVED] precalc clipping info
    private short[]	propWdt;        // [RESERVED] precalc clipping info
    private short[]	propHgt;        // [RESERVED] precalc clipping info
    private Hashtable    m_Hashtable;    // [RESERVED] used to map special chars
    private StringBuffer strBuff = new StringBuffer();
    
    private int         i;              // [RESERVED] generic loops
    private int         chr;            // [RESERVED] a char value to be processed
    private int         len;            // [RESERVED] lenght of string (in characters)
    private int         lenpix;         // [RESERVED] string's pixels lenght
    
    private int         scrWidth;       // [RESERVED] width of screen
    private int         scrHeight;      // [RESERVED] height of screen
    
    private boolean     bViewArea;                      // [RESERVED] T/F view area is active
    private int         viewArea_minX, viewArea_maxX;   // [RESERVED] view area settings
    private int         spacing;                        // [RESERVED] pixels character spacing
    
    private final byte    FONT_HEADER           = 4;    // Header fields of Font Data array
    private final byte    FONT_MEMDATA_FIELDS   = 3;    // # fields for each character data entry

    /*
     *  Font Constructicon
     *
     *  @param  fname   filename of an image containing the font images
     *  @param  mem     table containing data font definition (see FontSystem)
     */
    public FontSingle( FontSystem fs, String fname, short[] mem ) {
        m_FS = fs;
        short   NUMCHARS;                       // # characters to set
        short   CHARSXLINE;                     // # characters x line in the font-set
        short	offsetx = 0;
        
        try {
            // Creates a hashtable to retrieve non-standard characters
            // and adds hashkeys to translates them into Font's index values.
            m_Hashtable = new Hashtable();
            for( i=0, len=m_FS.mem_non_ascii.length(), chr=m_FS.NON_ASCII; i < len; i++ ) {
                m_Hashtable.put( new Integer(m_FS.mem_non_ascii.charAt(i)), new Integer(chr+i) );
            }
            
            // Loads a charset image
            this.img = Image.createImage(fname);
            
            // Gets font data definition from a table
            height = (short)mem[0];
            chroffset = (short)mem[1];
            NUMCHARS = (short)(mem[2] - chroffset + 1);
            CHARSXLINE = (short)mem[3];
            spacing = 1;
            
            propClipX = new short[NUMCHARS];
            propClipY = new short[NUMCHARS];
            propWdt = new short[NUMCHARS];
            propHgt = new short[NUMCHARS];
            propOffY = new short[NUMCHARS];
            
            for( i=0; i<NUMCHARS; i++ ) {
                // -- Pre-calc X/Width clip'n blit values for each character
                propWdt[i] = (byte)mem[FONT_HEADER+(i*FONT_MEMDATA_FIELDS)];
                if( (i % CHARSXLINE) == 0 ) offsetx = 0;
                propClipX[i] = offsetx;
                offsetx += propWdt[i];
                // if char width is not pair, then skip 1 pixel to force next crop-x at pair
                if( (propWdt[i] % 2) != 0 ) offsetx++;
                
                // -- Pre-calc Y/Height  clip'n blit values for each character
                propClipY[i] = (short)((i/CHARSXLINE)*height);
                propOffY[i] = (byte)mem[FONT_HEADER+(i*FONT_MEMDATA_FIELDS+1)];
                propHgt[i] = (byte)mem[FONT_HEADER+(i*FONT_MEMDATA_FIELDS+2)];
            }
        } catch(Exception _ex) {
//            System.out.println(">>> [FontSingle] "+ _ex);
        }
    }
    
    public void init( Gui gui ) {
        scrWidth = gui.getScreenWidth();
        scrHeight = gui.getScreenHeight();
    }
    
    /**
     *  Sets the view area to the given coordinates (only horizontal, vertical is not supported yet).
     *
     *  @param  x   x cordinate of the view area
     *  @param  w   width of the view area
     */
    public void setViewArea( int x, int w ) {
        bViewArea = true;
        viewArea_minX = x;
        viewArea_maxX = (viewArea_minX + w);
    }
    
    /**
     *  Draws a string from first character assuming first character at
     *  offset 0 and last character < str.Length().
     *  @param  str a String text to be drawn
     *  @param  x   x screen position
     *  @param  y   y screen position
     *  @param  g   a Graphics object
     */
    public void drawString( final String str, int x, int y, final Graphics g ) {
        drawString( str, 0, str.length(), x, y,  g );
    }
    
    /**
     *  Draws a string from first character assuming first character at
     *  offset 0 and last character < str.Length().
     *  @param  str     a String text to be drawn
     *  @param  ofst    offset of the first character to draw
     *  @param  len     length of characters to draw
     *  @param  x       x screen position
     *  @param  y       y screen position
     *  @param  g       a Graphics object
     */
    public void drawString( final String str, int ofst, int len, int x, int y, final Graphics g ) {
        strBuff.setLength(0);
        strBuff.append(str);
        drawString( strBuff, ofst, len, x, y,  g );
    }
    
    /**
     *  Draws a string from first character assuming first character at
     *  offset 0 and last character < str.Length().
     *  @param  strbf   a StringBufer text to be drawn
     *  @param  x       x screen position
     *  @param  y       y screen position
     *  @param  g       a Graphics object
     */
    public void drawString( final StringBuffer strbf, int x, int y, final Graphics g ) {
        drawString( strbf, 0, strbf.length(), x, y, g );
    }

    public void drawString(Graphics g, int x, int y, char c) {

    }

    public void drawString(Graphics g, int x, int y, StringBuffer buffer, int start, int count) {

    }

    
    /**
     *  Draws a text in the display.
     *
     *  @param  strbf   A StringBuffer text
     *  @param  ofst    offset of the first character to draw
     *  @param  len     length of characters to draw
     *  @param  x       x screen position
     *  @param  y       y screen position
     *  @param  g       a Graphics reference
     */
    public void drawString( final StringBuffer strbf, int ofst, int len,
            int x, int y, final Graphics g ) {
        
        int skip;
        int xx;         // temp. calc clipping for view area (if setted)
        int ww;         // temp. calc clipping for view area (if setted)
        int xinit;      // remember original x pos. to do a linefeed
        int ofstEnd = (ofst+len);
        
        if( strbf == null ) return;                         // nothing to paint
        if( ofst < 0 || ofstEnd > strbf.length() ) return; // out of range
        
        xinit = x;
        
        for( i=ofst; i<ofstEnd; i++ ) {
            chr = strbf.charAt(i);
            
            skip = checkLF( strbf, i );
            if( skip > 0 ) {
                i += (skip-1);
                x = xinit;
                y += height;
            } else {
                if( chr < chroffset || chr >= m_FS.NON_ASCII ) chr = translate_char(chr);
                chr -= chroffset;
                
                // -- copy x and width of char to calculate blitting coordinates
                xx = x; ww = propWdt[chr];
                
                // -- if view area is set, calculates new clip values
                //    (generally a view area is set to 0,0,screenWidth,screenHeight)
                if( bViewArea == true ) {
                    if( x < viewArea_minX ) {
                        ww = ww - ( viewArea_minX - x );
                        if( ww > 0 ) xx = viewArea_minX;
                    }
                    if( (x+ww) >= viewArea_maxX ) ww = (viewArea_maxX - x);
                }
                
                // -- draw char only if visible
                if( ww > 0 ) {
                    g.setClip(xx, (y + propOffY[chr]), ww, propHgt[chr]);
                    g.drawImage(img, (x - propClipX[chr]), (y - propClipY[chr]), Graphics.TOP|Graphics.LEFT);
                }
                
                // -- calculate x position for next character
                x += (propWdt[chr] + spacing);
                
            }   // end If( bEscape == false )
        }   // end For
        
        // -- reset view area
        bViewArea = false;
        
        /// -- reset clip to whole screen (hard-coded for better performance)
        //     to avoid problems with further draw calls
        g.setClip( 0, 0, scrWidth, scrHeight );
    }
    
    
    /**
     *  Sets the pixel spacing between characters. Use negative numbers to override previous characters.
     *
     *  @param  pixels     number of pixels between each caracter
     */
    public final void setSpacing( int pixels ) { spacing = pixels; }
    
    /**
     *  Returns the pixel spacing between characters.
     *
     *  @return  number of pixels between each caracter
     */
    public final int getSpacing() { return spacing;  }
    
    /**
     *  Gets the length of String in pixels.
     *
     *  @param  str     String to get its lenght in pixels
     *  @return number of pixels in the string
     */
    public final int getLenPixelString( final String str ) {
        strBuff.setLength(0);
        strBuff.append(str);
        return getLenPixelString( strBuff, 0, strBuff.length() );
    }

    /**
     *  Gets the length of sub String in pixels.
     *
     *  @param      str     String to get its lenght in pixels
     *  @param      ofst    character offset of string
     *  @param      len     character length of the sub string
     *  @return number of pixels in the sub string
     */
    public final int getLenPixelString( final String str, int ofst, int len ) {
        strBuff.setLength(0);
        strBuff.append(str);
        return getLenPixelString( strBuff, ofst, len );
    }
    
    /**
     *  Gets the length of StringBuffer in pixels.
     *
     *  @param      strbf   StringBuffer to get its lenght in pixels
     *  @return number of pixels in the sub string
     */
    public final int getLenPixelString( final StringBuffer strbf ) {
        return getLenPixelString( strbf, 0, strbf.length() );
    }

    /**
     *  Gets the length of sub StringBuffer in pixels.
     *
     *  @param      strbf   StringBuffer to get its lenght in pixels
     *  @param      ofst    character offset of string
     *  @param      len     character length of the sub string
     *  @return number of pixels in the sub string
     */
    public int getLenPixelString( final StringBuffer strbf, final int ofst, final int len ) {
        int skip;
        int ofstEnd = (ofst+len);
        
        for( i=ofst, lenpix=0; i<ofstEnd; i++ ) {
            chr = strbf.charAt(i);
            skip = checkLF( strbf, i );
            if( skip == 0 ) {         // character is NOT a Linefeed
                if( chr >= m_FS.NON_ASCII ) chr = translate_char( chr );
                lenpix += ( propWdt[ chr-chroffset ] + spacing );
            }
        }
        return (lenpix - spacing);
    }
    
    public final int getLenPixel( int chr ) {
        if( chr != '\n' && chr != '\r' ) {      // Linefeeds not allowed
            if( chr >= m_FS.NON_ASCII ) chr = translate_char( chr );
            return propWdt[ chr-chroffset ];
        }
        return 0;
    }
    
    
    /**
     * Returns the height of the string considering Linefeeds breaks.
     *
     * @return int height in pixels of the string including line breaks.
     */
    public int getStringHeight( final String str ) {
        int chr;
        int len;
        int skip;
        lenpix = 0;
        if( str != null ) {
            len = str.length();
            for( i=0, lenpix=height; i<len; i++ ) {
                chr = str.charAt(i);
                skip = checkLF( str, i );
                if( skip > 0 ) {
                    i += (skip-1);
                    lenpix += height;
                }
            }
        }
        return  lenpix;
    }
    
    /**
     *  Checks the presence of a CR (Mac), LF (Unix) or CR+LF (windows)
     *  in a string.
     *  Returns the new index position considering any linefeed found.
     *
     *  @param  str a String text to evaluate Linefeed.
     *  @pos    pos current index position to start checking
     *  @return new index position assuming a linefeed was found.
     */
    public final int checkLF( final String str, int pos ) {
        strBuff.setLength(0);
        strBuff.append(str);
        return checkLF( strBuff, pos );
    }

    /**
     *  Checks the presence of a CR (Mac), LF (Unix) or CR+LF (windows)
     *  in a string.
     *  Returns the new index position considering any linefeed found.
     *
     *  @param  strbf   a Stringuffer text to evaluate Linefeed.
     *  @pos    pos     current index position to start checking
     *  @return number of characters to skip (1 or 2 depending of linefeed syntax)
     */
    public final int checkLF( final StringBuffer strbf, int pos ) {
        int chr;
        int skip = 0;
        if( strbf != null ) {
            chr = strbf.charAt(pos);
            if( chr == '\r' ) {
                skip++;
                if( (pos+1) < strbf.length() ) {
                    if( strbf.charAt(pos+1) == '\n' ) {
                        skip++;
                    }
                }
            }
            if( chr == '\n' ) {
                skip++;
            }
        }
        return skip;
    }
    
    /**
     * Returns the font height.
     *
     * @return int  font height in pixels
     */
    public int getHeight() {
        return  height;
    }
    
    /*
     *  Translates a non-standard character (equals or higher than NON_ASCII )
     *  to an index value that Font will use to access font's data array.
     */
    private int translate_char( int chr ) {
        Object obj = m_Hashtable.get( new Integer(chr) );
        if( obj == null ) {
            chr = 126;      // character not found. shows a garbage character
        } else {
            chr = ((Integer)obj).intValue();
        }
        return chr;
    }
    
    //=======================================================
    
    public String[] splitStrLF( final String str ) {
        int skip;
        int numLines = 0;
        int i;
        int start = 0;
        int end = 0;
        String[] astr;
        if( str == null ) return null;
        
        for( i=0; i< str.length(); i++ ) {
            skip = checkLF(str, i);
            if( skip > 0 || i == (str.length()-1) ) {
                if( skip > 0 ) {
                    i += (skip-1);
                }
                numLines++;
            }
        }
        astr = new String[numLines];
        start = 0;
        i = 0;
        numLines = 0;
        do {
            skip = checkLF(str, i);
            if( skip > 0 ) {
                astr[numLines] = new String();
                astr[numLines] = str.substring(start, i);
                start = (i + skip);
                i += (skip-1);
                numLines++;
            }
            i++;
        } while ( i < str.length() );
        
        if( start < i ) {
            astr[numLines] = new String();
            astr[numLines] = str.substring(start, i);
        }
        
        return astr;
    }
    
       
    /**
     * Fits a string line in a space area.
     * The given string is NOT modified (CR nor LF are inserted into it).
     *
     *  @param  str     a String to fit
     *  @param  maxPix  area (in pixels width) to try to fit the line
     *  @param  vec     a Vector that stores an int array with the offset
     *                  at [0] and length at [1] of each line that was fitted
     *  @return number of lines. same as vector.size()
     *
     */
    public final int fitStringPixel( final String str, int maxPix, final Vector vec ) {
        int     chr = 0;
        int     pos = 0;
        int     startPos;
        int     endPos = 0;
        int     m_numLines = 0;         // same as vec.size()
        int     lastSpacePos = 0;
        int     pixCount;
        int     skip;
        int     len;
        int     chrW;
        
        if( str == null || vec == null ) {
            return 0;
        }
        
        vec.removeAllElements();
        len = str.length();
        
        while( pos < len ) {
            pixCount = 0;
            startPos = pos;
            lastSpacePos = -1;
            
            chr = str.charAt(pos);
            skip = checkLF(str, pos);
            if( skip > 0 ) {
                pos += skip;
            } else {
                chrW = getLenPixel(chr);
                while( (pixCount+chrW) < maxPix && pos < len ) {
                    if( chr == ' ' ) {
                        lastSpacePos = pos;
                    }
                    pixCount += (chrW + getSpacing());
                    pos++;
                    if( pos < len ) {
                        chr = str.charAt(pos);
                        skip = checkLF(str, pos);
                        if( skip > 0 ) {
                            pixCount = maxPix;
                            pos += skip;
                        } else {
                            chrW = getLenPixel(chr);
                        }
                    }
                }
            }
            
            m_numLines++;
            
            endPos = pos;
            if( pos < len ) {
                // si ultimo caracter que no entro es espacio, simplemente skipearlo
                // para q no sea considerado en el inicio de proxima linea
                if( chr == ' ' ) {
                    pos++;
                }
                if( chr != ' ' &&
                        chr != '\r' &&
                        chr != '\n' ) {
                    if( lastSpacePos != -1 ) {
                        endPos = lastSpacePos;
                        pos = endPos+1;
                    }
                }
            }
            
            // Stores the offset and length of the fitted line
            int[] aint = new int[2];
            aint[0] = startPos;
            aint[1] = (endPos - startPos);
            vec.addElement(aint);
//            test_output( str, aint[0], aint[1] );
        }
        
        return m_numLines;
    }
       
//    public void test_output( String str, int start, int end ) {
//        for( int i=start; i<(start+end); i++ ) {
//            System.out.print( str.charAt(i) );
//        }
//        System.out.println("");
//    }
       

    /** 
     * Devuelve la longitud en pixels de un conjunto de carateres "imprimibles".
     * 
     * @param buffer Contiene el conjunto de caracteres.
     * @param start Primer caracter del conjunto
     * @param count Cantidad de caracteres.
     * 
     * @return Devuelve la longitud de caracteres "imprimibles".
     */
    public int calcLength(StringBuffer buffer, int start, int count) {
	return 0;
    }    
}
